/**
 * covert canvas to image and save the image file
 */
var Canvas2Image = (function () {
   var $support = (function () {
      let canvas = document.createElement('canvas')
      let ctx = canvas.getContext('2d')
      return {
         canvas: !!ctx,
         imageData: !!ctx.getImageData,
         dataURL: !!canvas.toDataURL,
         btoa: !!window.btoa
      }
   })()

   var downloadMime = 'image/octet-stream'

   function scaleCanvas (canvas, width, height) {
      var w = canvas.width
      var h = canvas.height
      if (width === undefined) {
         width = w
      }
      if (height === undefined) {
         height = h
      }

      var retCanvas = document.createElement('canvas')
      var retCtx = retCanvas.getContext('2d')
      retCanvas.width = width
      retCanvas.height = height
      retCtx.drawImage(canvas, 0, 0, w, h, 0, 0, width, height)
      return retCanvas
   }

   /**
    * 获取data url
    * @param {canvas} canvas
    * @param {string} type 'png|jpg|jpeg|bmp|gif'
    * @param {number} width
    * @param {number} height
    *
    * @returns 返回canvas.toDataURL
    */
   function getDataURL (canvas, type, width, height) {
      type = fixType(type)
      canvas = scaleCanvas(canvas, width, height)

      return canvas.toDataURL(type)
   }

   function saveFile (strData) {
      document.location.href = strData
   }

   function genImage (strData) {
      var img = document.createElement('img')
      img.src = strData
      return img
   }

   function fixType (type) {
      type = type.toLowerCase().replace(/jpg/i, 'jpeg')
      var r = type.match(/png|jpeg|bmp|gif/)[0]
      return 'image/' + r
   }

   function encodeData (data) {
      if (!window.btoa) {
         throw new Error('btoa undefined')
      }
      var str = ''
      if (typeof data === 'string') {
         str = data
      } else {
         for (var i = 0; i < data.length; i++) {
            str += String.fromCharCode(data[i])
         }
      }

      return btoa(str)
   }

   function getImageData (canvas) {
      var w = canvas.width
      var h = canvas.height
      return canvas.getContext('2d').getImageData(0, 0, w, h)
   }

   function makeURI (strData, type) {
      return 'data:' + type + ';base64,' + strData
   }

   /**
    * create bitmap image
    * 按照规则生成图片响应头和响应体
    */
   var genBitmapImage = function (oData) {
      //
      // BITMAPFILEHEADER: http://msdn.microsoft.com/en-us/library/windows/desktop/dd183374(v=vs.85).aspx
      // BITMAPINFOHEADER: http://msdn.microsoft.com/en-us/library/dd183376.aspx
      //

      var biWidth = oData.width
      var biHeight = oData.height
      var biSizeImage = biWidth * biHeight * 3
      var bfSize = biSizeImage + 54 // total header size = 54 bytes

      //
      //  typedef struct tagBITMAPFILEHEADER {
      //    WORD bfType;
      //    DWORD bfSize;
      //    WORD bfReserved1;
      //    WORD bfReserved2;
      //    DWORD bfOffBits;
      //  } BITMAPFILEHEADER;
      //
      var BITMAPFILEHEADER = [
         // WORD bfType -- The file type signature; must be "BM"
         0x42,
         0x4d,
         // DWORD bfSize -- The size, in bytes, of the bitmap file
         bfSize & 0xff,
         (bfSize >> 8) & 0xff,
         (bfSize >> 16) & 0xff,
         (bfSize >> 24) & 0xff,
         // WORD bfReserved1 -- Reserved; must be zero
         0,
         0,
         // WORD bfReserved2 -- Reserved; must be zero
         0,
         0,
         // DWORD bfOffBits -- The offset, in bytes, from the beginning of the BITMAPFILEHEADER structure to the bitmap bits.
         54,
         0,
         0,
         0
      ]

      //
      //  typedef struct tagBITMAPINFOHEADER {
      //    DWORD biSize;
      //    LONG  biWidth;
      //    LONG  biHeight;
      //    WORD  biPlanes;
      //    WORD  biBitCount;
      //    DWORD biCompression;
      //    DWORD biSizeImage;
      //    LONG  biXPelsPerMeter;
      //    LONG  biYPelsPerMeter;
      //    DWORD biClrUsed;
      //    DWORD biClrImportant;
      //  } BITMAPINFOHEADER, *PBITMAPINFOHEADER;
      //
      var BITMAPINFOHEADER = [
         // DWORD biSize -- The number of bytes required by the structure
         40,
         0,
         0,
         0,
         // LONG biWidth -- The width of the bitmap, in pixels
         biWidth & 0xff,
         (biWidth >> 8) & 0xff,
         (biWidth >> 16) & 0xff,
         (biWidth >> 24) & 0xff,
         // LONG biHeight -- The height of the bitmap, in pixels
         biHeight & 0xff,
         (biHeight >> 8) & 0xff,
         (biHeight >> 16) & 0xff,
         (biHeight >> 24) & 0xff,
         // WORD biPlanes -- The number of planes for the target device. This value must be set to 1
         1,
         0,
         // WORD biBitCount -- The number of bits-per-pixel, 24 bits-per-pixel -- the bitmap
         // has a maximum of 2^24 colors (16777216, Truecolor)
         24,
         0,
         // DWORD biCompression -- The type of compression, BI_RGB (code 0) -- uncompressed
         0,
         0,
         0,
         0,
         // DWORD biSizeImage -- The size, in bytes, of the image. This may be set to zero for BI_RGB bitmaps
         biSizeImage & 0xff,
         (biSizeImage >> 8) & 0xff,
         (biSizeImage >> 16) & 0xff,
         (biSizeImage >> 24) & 0xff,
         // LONG biXPelsPerMeter, unused
         0,
         0,
         0,
         0,
         // LONG biYPelsPerMeter, unused
         0,
         0,
         0,
         0,
         // DWORD biClrUsed, the number of color indexes of palette, unused
         0,
         0,
         0,
         0,
         // DWORD biClrImportant, unused
         0,
         0,
         0,
         0
      ]

      var iPadding = (4 - ((biWidth * 3) % 4)) % 4

      var aImgData = oData.data

      var strPixelData = ''
      var biWidth4 = biWidth << 2
      var y = biHeight
      var fromCharCode = String.fromCharCode

      do {
         var iOffsetY = biWidth4 * (y - 1)
         var strPixelRow = ''
         for (var x = 0; x < biWidth; x++) {
            var iOffsetX = x << 2
            strPixelRow +=
               fromCharCode(aImgData[iOffsetY + iOffsetX + 2]) +
               fromCharCode(aImgData[iOffsetY + iOffsetX + 1]) +
               fromCharCode(aImgData[iOffsetY + iOffsetX])
         }

         for (var c = 0; c < iPadding; c++) {
            strPixelRow += String.fromCharCode(0)
         }

         strPixelData += strPixelRow
      } while (--y)

      var strEncoded =
         encodeData(BITMAPFILEHEADER.concat(BITMAPINFOHEADER)) +
         encodeData(strPixelData)

      return strEncoded
   }

   /**
    * saveAsImage
    * @param canvasElement
    * @param {String} image type
    * @param {Number} [optional] png width
    * @param {Number} [optional] png height
    */
   var saveAsImage = function (canvas, width, height, type) {
      if ($support.canvas && $support.dataURL) {
         if (typeof canvas === 'string') {
            canvas = document.getElementById(canvas)
         }

         if (type === undefined) {
            type = 'png'
         }

         type = fixType(type)

         var strData = ''
         if (/bmp/.test(type)) {
            var data = getImageData(scaleCanvas(canvas, width, height))
            strData = genBitmapImage(data)
            saveFile(makeURI(strData, downloadMime))
         } else {
            strData = getDataURL(canvas, type, width, height)
            saveFile(strData.replace(type, downloadMime))
         }
      }
   }

   var convertToImage = function (canvas, width, height, type) {
      if ($support.canvas && $support.dataURL) {
         if (typeof canvas === 'string') {
            canvas = document.getElementById(canvas)
         }

         if (type === undefined) {
            type = 'png'
         }

         type = fixType(type)

         var strData = ''
         if (/bmp/.test(type)) {
            var data = getImageData(scaleCanvas(canvas, width, height))
            strData = genBitmapImage(data)
            return genImage(makeURI(strData, 'image/bmp'))
         } else {
            strData = getDataURL(canvas, type, width, height)
            return genImage(strData)
         }
      }
   }

   return {
      getImgBase64String: getDataURL,
      getJPEGBase64String: function (canvas, width, height) {
         return getDataURL(canvas, 'jpeg', width, height)
      },
      getPNGBase64String: function (canvas, width, height) {
         return getDataURL(canvas, 'png', width, height)
      },
      getGIFBase64String: function (canvas, width, height) {
         return getDataURL(canvas, 'gif', width, height)
      },
      getBMPBase64String: function (canvas, width, height) {
         return getDataURL(canvas, 'bmp', width, height)
      },

      saveAsImage: saveAsImage,
      saveAsPNG: function (canvas, width, height) {
         return saveAsImage(canvas, width, height, 'png')
      },
      saveAsJPEG: function (canvas, width, height) {
         return saveAsImage(canvas, width, height, 'jpeg')
      },
      saveAsGIF: function (canvas, width, height) {
         return saveAsImage(canvas, width, height, 'gif')
      },
      saveAsBMP: function (canvas, width, height) {
         return saveAsImage(canvas, width, height, 'bmp')
      },

      convertToImage: convertToImage,
      convertToPNG: function (canvas, width, height) {
         return convertToImage(canvas, width, height, 'png')
      },
      convertToJPEG: function (canvas, width, height) {
         return convertToImage(canvas, width, height, 'jpeg')
      },
      convertToGIF: function (canvas, width, height) {
         return convertToImage(canvas, width, height, 'gif')
      },
      convertToBMP: function (canvas, width, height) {
         return convertToImage(canvas, width, height, 'bmp')
      }
   }
})()

export default Canvas2Image
